﻿using Microsoft.ExtendedReflection.Collections;
using Microsoft.ExtendedReflection.ComponentModel;
using Microsoft.ExtendedReflection.Instrumentation;
using Microsoft.ExtendedReflection.Logging;
using Microsoft.ExtendedReflection.Metadata;
using Microsoft.ExtendedReflection.Metadata.Names;
using Microsoft.ExtendedReflection.Monitoring;
using Microsoft.ExtendedReflection.Remote;
using Microsoft.ExtendedReflection.Utilities;
using Microsoft.ExtendedReflection.Utilities.Safe;
using Microsoft.Moles.Engine;
using Microsoft.Moles.Framework.Engine;
using Microsoft.Moles.Framework.Moles;
using MolesVS2012.Host.Host;
using Microsoft.VisualStudio.TestTools.Common;
using Microsoft.VisualStudio.TestTools.Execution;
using Microsoft.VisualStudio.TestTools.TestAdapter;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.Remoting;
using System.Threading;

namespace MolesVS2012.Host.Agent
{
    [Serializable]
    internal sealed class HostTestAdapterDriver : ComponentElementBase, ITestAdapter, IBaseAdapter
    {
        private Bitness _bitness = Bitness.Unsupported;
        private readonly object syncLock = new object();
        private readonly string assembly;
        private bool _diagnostic;
        private string _silverlightVersion;
        private MolesHostAdapter _hostAdapter;
        private MolesAgentRemotingManager _remotingManager;
        private InstrumentationReflectionResult instrumentationResult;
        private int waitingClient;
        private ProcessStartInfo _processStartInfo;
        private Process _process;
        private int? _exitCode;
        private MolesAgentAdapter _agentAdapter;

        public bool IsAlive
        {
            get
            {
                MolesHostAdapter hostAdapter;
                return this.TryGetHostAdapter(out hostAdapter);
            }
        }

        public MolesAgentRemotingManager RemotingManager
        {
            get
            {
                if (this._remotingManager == null)
                    this._remotingManager = new MolesAgentRemotingManager(this.Host);
                return this._remotingManager;
            }
        }

        private MolesAgentAdapter AgentAdapter
        {
            get
            {
                if (this._agentAdapter == null)
                    this._agentAdapter = this.Host.GetService<MolesAgentAdapter>();
                return this._agentAdapter;
            }
        }

        public HostTestAdapterDriver(IComponent host, string assembly)
            : base(host)
        {
            this.assembly = assembly;
        }

        private bool TryGetHostAdapter(out MolesHostAdapter hostAdapter)
        {
            Process process = this._process;
            hostAdapter = this._hostAdapter;
            if (process != null && hostAdapter != null)
                return !process.HasExited;
            else
                return false;
        }

        private MolesHostAdapter EnsureHostAdapter()
        {
            MolesHostAdapter hostAdapter;
            if (!this.TryGetOrCreateHostAdapter(out hostAdapter))
                throw new InvalidOperationException("Could not start Moles host. Please review the Test Run Errors for more information.");
            else
                return hostAdapter;
        }

        private bool IsConnected()
        {
            try
            {
                Process process = this._process;
                if (process == null || process.HasExited)
                    return false;
                MolesHostAdapter molesHostAdapter = this._hostAdapter;
                try
                {
                    if (molesHostAdapter != null)
                    {
                        if (molesHostAdapter.Ping())
                            goto label_7;
                    }
                    this.KillProcess();
                    return false;
                }
                catch (RemotingException ex)
                {
                    this.KillProcess();
                    return false;
                }
            label_7:
                return true;
            }
            catch (InvalidOperationException ex)
            {
                return false;
            }
        }

        private bool TryGetOrCreateHostAdapter(out MolesHostAdapter hostAdapter)
        {
            IEventLog log = this.Host.Log;
            while (true)
            {
                int num = 0;
                lock (this.syncLock)
                {
                    if (!this.IsConnected())
                    {
                        log.LogMessage("Agent", "launching host process for {0}", new object[1]
            {
              (object) this.assembly
            });
                        try
                        {
                            string local_2;
                            if (!this.TryGetProcessStartInfo(out local_2))
                            {
                                log.LogError("Missing Wiki Topic", "Agent", "failed to setup the hosted process for {0}", new object[1]
                {
                  (object) this.assembly
                });
                                hostAdapter = (MolesHostAdapter)null;
                                return false;
                            }
                            else
                            {
                                log.LogMessage("Agent", "{0}>{1} {2}", (object)this._processStartInfo.WorkingDirectory, (object)this._processStartInfo.FileName, (object)this._processStartInfo.Arguments);
                                if (log.Verbosity >= LogLevel.Verbose)
                                {
                                    using (DumpTreeWriter resource_0 = log.CreateDumpWriter("Agent", "host environment variables"))
                                    {
                                        foreach (DictionaryEntry item_0 in Enumerable.Cast<DictionaryEntry>((IEnumerable)this._processStartInfo.EnvironmentVariables))
                                            resource_0.WriteLine("{0}={1}", item_0.Key, item_0.Value);
                                    }
                                }
                                this._process = new Process();
                                this._process.StartInfo = this._processStartInfo;
                                this._process.EnableRaisingEvents = true;
                                this._process.Exited += new EventHandler(this.processExited);
                                if (!this._process.Start())
                                {
                                    log.LogError("Missing Wiki Topic", "Agent", "failed to launch host process ({0} {1})", new object[2]
                  {
                    (object) this._processStartInfo.FileName,
                    (object) this._processStartInfo.Arguments
                  });
                                    hostAdapter = (MolesHostAdapter)null;
                                    this._process = (Process)null;
                                    return false;
                                }
                                else
                                {
                                    log.LogMessage("Agent", "waiting for host");
                                    Interlocked.Exchange(ref this.waitingClient, 1);
                                    Thread.Sleep(1000);
                                    if (this._process.HasExited || !RemoteServer.WaitTillClientReady(local_2))
                                    {
                                        Interlocked.Exchange(ref this.waitingClient, 0);
                                        log.LogError("Missing Wiki Topic", "Agent", "wait for host {0} failed", new object[1]
                    {
                      (object) this._process.Id
                    });
                                        hostAdapter = (MolesHostAdapter)null;
                                        return false;
                                    }
                                    else
                                    {
                                        Interlocked.Exchange(ref this.waitingClient, 0);
                                        if (this._process.HasExited)
                                        {
                                            log.LogError("Missing Wiki Topic", "Agent", "host exited before connecting");
                                            hostAdapter = (MolesHostAdapter)null;
                                            return false;
                                        }
                                        else
                                        {
                                            IService local_6;
                                            if (!this.RemotingManager.GetConnector().TryGetService(typeof(MolesHostAdapter), out local_6) || (hostAdapter = local_6 as MolesHostAdapter) == null)
                                            {
                                                log.LogError("Missing Wiki Topic", "Agent", "failed to get service from host adapter");
                                                hostAdapter = (MolesHostAdapter)null;
                                                return false;
                                            }
                                            else
                                            {
                                                log.LogMessage("Agent", "host process connected");
                                                this._hostAdapter = hostAdapter;
                                                this._hostAdapter.Initialize(this.AgentAdapter.RunContext);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        catch (RemotingException exception_0)
                        {
                            int temp_99 = num;
                            int temp_100 = 1;
                            int local_1_1 = temp_99 + temp_100;
                            int temp_102 = 2;
                            if (temp_99 < temp_102)
                            {
                                log.LogMessage("Agent", "Remoting exception while connecting host, retrying");
                                continue;
                            }
                            else
                            {
                                log.LogError("Missing Wiki Topic", "Agent", "failed to connect with the host process");
                                this.KillProcess();
                                this.Host.Log.Flush();
                            }
                        }
                        catch (Exception exception_1)
                        {
                            log.LogErrorFromException(exception_1, "Missing Wiki Topic", "Agent", "failed to start Moles test host");
                            this.KillProcess();
                            this.Host.Log.Flush();
                        }
                    }
                    return this.TryGetHostAdapter(out hostAdapter);
                }
            }
        }

        private void processExited(object sender, EventArgs e)
        {
            Process process = this._process;
            try
            {
                if (process == null || !process.HasExited)
                    return;
                this._exitCode = new int?(process.ExitCode);
                int? nullable = this._exitCode;
                if ((nullable.GetValueOrDefault() != 0 ? 1 : (!nullable.HasValue ? 1 : 0)) == 0)
                    return;
                this.Host.Log.LogError("Missing Wiki Topic", "Agent", "host process exited with {0}. See {1} for more information.", new object[2]
        {
          (object) MolesVsHostExitCodes.Describe(this._exitCode.Value),
          (object) HostTestAdapterDriver.GetWebUrlWikiTopic(this._exitCode.Value)
        });
            }
            catch (InvalidOperationException ex)
            {
            }
        }

        private static string GetWebUrlWikiTopic(int exitCode)
        {
            string name;
            if (ExitCodes.TryGetName(exitCode, out name, typeof(ExitCodes), typeof(MolesVsHostExitCodes)))
                return "http://research.microsoft.com/en-us/um/redmond/projects/pex/wiki/Exit Code " + name.Replace("+", "%20") + ".html";
            else
                return "http://research.microsoft.com/en-us/um/redmond/projects/pex/wiki/Exit Codes";
        }

        private bool TryGetProcessStartInfo(out string clientID)
        {
            clientID = (string)null;
            string directoryName1 = Path.GetDirectoryName(typeof(MolesAgentAdapter).Assembly.Location);
            string directoryName2 = Path.GetDirectoryName(directoryName1);
            string str = Path.Combine(directoryName2, "PublicAssemblies");
            SafeSet<string> safeSet = new SafeSet<string>((IEqualityComparer<string>)StringComparer.OrdinalIgnoreCase)
      {
        directoryName1,
        str,
        directoryName2,
        MolesInstallHelper.GetBinariesDirectory()
      };
            this._processStartInfo = (ProcessStartInfo)null;
            ShortAssemblyName[] _instrumentationProviderAssemblies = new ShortAssemblyName[1]
      {
        Metadata<MoleBase>.Assembly.SerializableName
      };
            string environmentVariable = Environment.GetEnvironmentVariable("COR_PROFILER");
            if (!SafeString.IsNullOrEmpty(environmentVariable))
            {
                this.Host.Log.LogVerbose("Agent", "registered profiler {0}", new object[1]
        {
          (object) environmentVariable
        });
                if (!string.Equals(environmentVariable, "{301EC75B-AD5A-459c-A4C4-911C878FA196}", StringComparison.OrdinalIgnoreCase))
                {
                    this.Host.Log.LogMessage("Missing Wiki Topic", "Agent", (object)"an unknown profiler {0} is already registered, overriding value.", (object)environmentVariable);
                    Environment.SetEnvironmentVariable("COR_PROFILER", (string)null);
                }
            }
            InstrumentationSettingsName instrumentationSettings;
            this.instrumentationResult = InstrumentationReflectionEngine.TryReflectInstrumentation(this.assembly, _instrumentationProviderAssemblies, safeSet.ToArray(), out instrumentationSettings);
            if (!this.TryValidateInstrumentationResult())
                return false;
            instrumentationSettings.Dump(this.Host.Log);
            this._bitness = instrumentationSettings.Bitness;
            this._diagnostic = instrumentationSettings.Diagnostic;
            if (!instrumentationSettings.TryGetSilverlightVersion(out this._silverlightVersion))
                this._silverlightVersion = (string)null;
            string runner;
            if (!this.TryGetProcessFileName(out runner))
                return false;
            this._processStartInfo = ControllerSetUp.GetMonitorableProcessStartInfo(runner, new string[1]
      {
        SafeString.Format("\"{0}\"", (object) this.assembly)
      }, instrumentationSettings, (string)null, (string)null, (string)null, 0 != 0, (string)null, 1 != 0, 0 != 0, ProfilerInteraction.Dispatch, MolesMetadata.InstrumentAssemblyName, (string)null, (string)null);
            ControllerSetUp.SetInstrumentationFlags(this._processStartInfo.EnvironmentVariables, MonitorInstrumentationFlags.EnableDetours);
            MolesHostAdapterManager service = this.Host.GetService<MolesHostAdapterManager>();
            this._processStartInfo.EnvironmentVariables[MolesVsHostEnvironmentSettings.ServerChannel.Name] = service.ServerChannel;
            MolesAgentRemotingManager remotingManager = this.RemotingManager;
            clientID = this.RemotingManager.ClientID;
            this._processStartInfo.EnvironmentVariables[MolesVsHostEnvironmentSettings.ClientID.Name] = clientID;
            if (this._diagnostic)
            {
                this._processStartInfo.EnvironmentVariables[MolesVsHostEnvironmentSettings.Diagnostic.Name] = "1";
                this.Host.Log.LogVerbose("Agent", "diagnostic on");
            }
            if (this._silverlightVersion != null)
            {
                this._processStartInfo.EnvironmentVariables[MolesVsHostEnvironmentSettings.SilverlightVersion.Name] = this._silverlightVersion;
                this.Host.Log.LogVerbose("Agent", "Silverlight version: {0}", new object[1]
        {
          (object) this._silverlightVersion
        });
            }
            this.InitializeHostData();
            this.Host.Log.LogVerbose("Agent", "vshost executable: {0}", new object[1]
      {
        (object) this._processStartInfo.FileName
      });
            if (MolesVsHostEnvironmentSettings.Driver.Value != null)
            {
                this._processStartInfo.Arguments = SafeString.Format("\"{0}\" {1}", (object)this._processStartInfo.FileName, (object)this._processStartInfo.Arguments);
                this._processStartInfo.FileName = MolesVsHostEnvironmentSettings.Driver.Value;
            }
            this._processStartInfo.WorkingDirectory = Path.GetDirectoryName(this.assembly);
            if (!MolesVsHostEnvironmentSettings.CreateWindow.Value)
                this._processStartInfo.CreateNoWindow = true;
            return true;
        }

        private void InitializeHostData()
        {
            IEngineOptions options = this.Host.Services.Options;
            if (options.Diagnostic)
            {
                this.Host.Log.LogVerbose("Agent", "enabling diagnostics");
                this._processStartInfo.EnvironmentVariables[MolesVsHostEnvironmentSettings.Diagnostic.Name] = "1";
                this._processStartInfo.EnvironmentVariables[MolesVsHostEnvironmentSettings.CreateWindow.Name] = "1";
            }
            this._processStartInfo.EnvironmentVariables[MolesVsHostEnvironmentSettings.Verbosity.Name] = EnumHelper.Format<LogLevel>((int)options.Verbosity);
        }

        private bool TryValidateInstrumentationResult()
        {
            IEventLog log = this.Host.Log;
            switch (this.instrumentationResult)
            {
                case InstrumentationReflectionResult.AssemblyDoesNotExist:
                    log.LogError("Missing Wiki Topic", "Agent", "could not find assembly {0}", new object[1]
          {
            (object) this.assembly
          });
                    return false;
                case InstrumentationReflectionResult.CannotLoadAssembly:
                    log.LogError("Missing Wiki Topic", "Agent", "could not load assembly {0}", new object[1]
          {
            (object) this.assembly
          });
                    return false;
                case InstrumentationReflectionResult.CannotReflectAssembly:
                    log.LogError("Missing Wiki Topic", "Agent", "could not reflect over assembly {0}", new object[1]
          {
            (object) this.assembly
          });
                    return false;
                case InstrumentationReflectionResult.MustTargetX86:
                    if (this._bitness != Bitness.x86)
                    {
                        log.LogError("Missing Wiki Topic", "Agent", "must target x86");
                        return false;
                    }
                    else
                        break;
                case InstrumentationReflectionResult.MustTargetX64OrItanium:
                    if (this._bitness == Bitness.x86)
                    {
                        log.LogError("Missing Wiki Topic", "Agent", "test assembly requires x64 host runner");
                        return false;
                    }
                    else
                        break;
                case InstrumentationReflectionResult.ExternalProfilerRegistered:
                    log.LogError("Missing Wiki Topic", "Agent", "external profiler already registered");
                    return false;
            }
            return true;
        }

        private bool TryGetProcessFileName(out string runner)
        {
            string directoryName = Path.GetDirectoryName(typeof(MolesAgentAdapter).Assembly.Location);
            string path;

            switch (this._bitness)
            {
                case Bitness.AnyCPU:
                case Bitness.x86:
                    path = Path.Combine(directoryName, "MolesVS2012.Host.exe");
                    break;
                case Bitness.x64:
                    path = Path.Combine(directoryName, "MolesVS2012.Host.exe");
                    break;
                default:
                    path = Path.Combine(directoryName, "MolesVS2012.Host.exe");
                    break;
            }

            if (path == null)
            {
                this.Host.Log.LogError("Missing Wiki Topic", "Agent", "unable to select a valid host executable");
                runner = (string)null;
                return false;
            }
            else if (!File.Exists(path))
            {
                this.Host.Log.LogError("Missing Wiki Topic", "Agent", "host executable '{0}' is missing", new object[1]
        {
          (object) path
        });
                runner = (string)null;
                return false;
            }
            else
            {
                runner = path;
                return true;
            }
        }

        public void Dispose()
        {
            this.KillProcess();
        }

        public void KillProcess()
        {
            lock (this.syncLock)
            {
                MolesAgentRemotingManager local_0 = this._remotingManager;
                if (local_0 != null)
                {
                    local_0.Close();
                    this._remotingManager = (MolesAgentRemotingManager)null;
                }
                Process local_1 = this._process;
                try
                {
                    if (local_1 != null)
                    {
                        if (!local_1.HasExited)
                        {
                            this.Host.Log.LogMessage("Agent", "killing host process");
                            local_1.Kill();
                            local_1.Dispose();
                            this._exitCode = new int?(-1073741510);
                        }
                        else
                            this._exitCode = new int?(local_1.ExitCode);
                    }
                }
                catch (InvalidOperationException exception_0)
                {
                }
                this._process = (Process)null;
                this._hostAdapter = (MolesHostAdapter)null;
            }
        }

        void ITestAdapter.Initialize(IRunContext runContext)
        {
            this.EnsureHostAdapter().Initialize(runContext);
        }

        void ITestAdapter.PreTestRunFinished(IRunContext runContext)
        {
            this.EnsureHostAdapter().PreTestRunFinished(runContext);
        }

        void ITestAdapter.ReceiveMessage(object obj)
        {
            this.EnsureHostAdapter().ReceiveMessage(obj);
        }

        void IBaseAdapter.AbortTestRun()
        {
            MolesHostAdapter hostAdapter;
            if (!this.TryGetHostAdapter(out hostAdapter))
                return;
            hostAdapter.AbortTestRun();
        }

        void IBaseAdapter.Cleanup()
        {
            MolesHostAdapter hostAdapter;
            if (!this.TryGetHostAdapter(out hostAdapter))
                return;
            hostAdapter.Cleanup();
        }

        void IBaseAdapter.PauseTestRun()
        {
            this.EnsureHostAdapter().PauseTestRun();
        }

        void IBaseAdapter.ResumeTestRun()
        {
            this.EnsureHostAdapter().ResumeTestRun();
        }

        void IBaseAdapter.StopTestRun()
        {
            MolesHostAdapter hostAdapter;
            if (!this.TryGetHostAdapter(out hostAdapter))
                return;
            hostAdapter.StopTestRun();
        }

        void IBaseAdapter.Run(ITestElement testElement, ITestContext testContext)
        {
            this.EnsureHostAdapter().Run(testElement, testContext);
        }
    }
}